package store;

import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.xpath.XPath;
import dataview.Cibi;
import dataview.CriterioRicerca;
import dataview.InfoCibo;
import dataview.Ordinamento;
import dataview.PerStringa;
import dataview.PerTermine;
import dataview.Termini;
import exceptions.DBException;
import exceptions.ElementoNonPresente;
import exceptions.NomeGiaInUso;

public class TuttiCibi extends BaseStore {
	private static TuttiCibi singleton;
	private CriterioRicerca ultimaRicerca;
	
	private TuttiCibi() {}
	
	public static TuttiCibi getTuttiCibi() {
		if (singleton == null) {
			singleton = new TuttiCibi();
			singleton.ultimaRicerca = BaseStore.getSettingsCibi();
		}
		return singleton;
	}
	
	/**
	 * Controlla che nel DB sia presente un cibo con il nome richiesto
	 * 
	 * @param cibo Nome del cibo da ricercare
	 * @return True sse il cibo e' presente nel DB, false altrimenti
	 * @throws DBException
	 */
	public boolean presente(String cibo) throws DBException {
		try {
			getCiboElement(cibo);
		} catch (ElementoNonPresente e) {
			return false;
		}
		return true;
	}
	
	/**
	 * Restituisce la lista degli ultimi cibi ricercati
	 * 
	 * @throws DBException
	 */
	public Cibi ultimiCercati() throws DBException {
		return ricerca(ultimaRicerca);
	}
	
	/**
	 * Inserisce nel database un cibo
	 * 
	 * @param cibo Il cibo da inserire (deve essere diverso da null)
	 * @throws DBException
	 * @throws NomeGiaInUso
	 */
	public void aggiungi(Cibo cibo) throws DBException, NomeGiaInUso {
		aggiungi(cibo, true);
	}

	private void aggiungi (Cibo cibo, boolean save) throws DBException, NomeGiaInUso {
		if (presente(cibo.getNome())) throw new NomeGiaInUso("Nel database e' gia' presente un cibo con nome = '" + cibo.getNome() + "'");
		getDB().getRootElement().getChild("cibi").addContent(cibo.toXml());
		if (save) save(false);
	}
	
	/**
	 * Elimina il cibo dal database
	 * 
	 * @param cibo Il cibo da eliminare
	 * @throws DBException
	 * @throws ElementoNonPresente Se il cibo passato come parametro non e' presente nel database
	 */
	public void elimina(Cibo cibo) throws DBException, ElementoNonPresente {
		Element c = getCiboElement(cibo.getNome());
		if (c == null) throw new ElementoNonPresente("Nel database non e' presente un cibo con nome = '" + cibo.getNome() + "'");
		getDB().getRootElement().getChild("cibi").removeContent(c);
		save(false);
	}
	
	public Cibi ricerca(CriterioRicerca criterio) throws DBException {
		if (criterio == null) return new Cibi();
		setSettingsCibi(criterio);
		ultimaRicerca = criterio;
		SortedSet<Cibo> cibi = new TreeSet<Cibo>(Cibo.getComparator(criterio.getOrdinamento() == Ordinamento.crescente));

		//XXX: quali sono le parti che devo andare a guardare??? Io ho pensato al nome e alla descrizione
		if (criterio instanceof PerStringa) {
			PerStringa c = (PerStringa)criterio;
			for (Cibo cibo : tuttiCibi())
				if (cibo.getNome().toLowerCase().contains(c.getStringa().toLowerCase()) || cibo.getInfo().getDescrizione().toLowerCase().contains(c.getStringa().toLowerCase())) {
					if (c.getAppare()) cibi.add(cibo);
				} else if (!c.getAppare()) cibi.add(cibo);
		}
		
		if (criterio instanceof PerTermine) {
			PerTermine c = (PerTermine)criterio;
			if (c.getEun()) {
				for (Cibo cibo : tuttiCibi())
					if (cibo.getTag() != null) for (Termine termine : cibo.getTag())
						if (termine.diTipo(c.getTermine())) {
							cibi.add(cibo);
							break;
						}
			} else {
				for (Cibo cibo : tuttiCibi()) {
					cibi.add(cibo);
					if (cibo.getTag() != null) for (Termine termine : cibo.getTag())
						if (termine.diTipo(c.getTermine())) {
							cibi.remove(cibo);
							break;
						}
				}
			}
		}
		return new Cibi(cibi);
	}
	
	/**
	 * Restituisce l'insieme di tutti i cibi salvati nel database
	 * 
	 * @throws DBException
	 */
	public Cibi tuttiCibi() throws DBException {
		try {
			Set<Cibo> cc = new TreeSet<Cibo>(Cibo.getComparator(true));
			for (Object cibo : XPath.selectNodes(getDB(), "/easycooking/cibi/cibo"))
				cc.add(Cibo.fromXml((Element)cibo));
			return new Cibi(cc);
		} catch (Exception e) {
			throw new DBException(e);
		}
	}
	
	/**
	 * Inserisci una serie di cibi garantendo che alla fine i nomi dei cibi siano unici; quando si
	 * inserisce un cibo C tale che esisteva gia' un cibo con lo stesso nome si aggiungono al nome di
	 * C delle X fino ad ottenere un nome non presente
	 * 
	 * @param cibi Collezione di cibi da inserire
	 * @throws DBException
	 * @throws NomeGiaInUso Non dovrebbe mai essere sollevata
	 */
	public void inserisciCibi(Cibi cibi) throws DBException {
		for (Cibo cibo : cibi) {
			while (presente(cibo.getNome()))
				cibo.setNome(cibo.getNome() + "X");
			try {
				aggiungi(cibo, false);
			} catch (NomeGiaInUso e) {
				e.printStackTrace();
			}
		}
	}
	
	/**
	 * Restituisce l'elemento che rappresenta il cibo richiesto
	 * 
	 * @param nome Il nome del cibo da cercare
	 * @return L'elemento che rappresenta il cibo dal nome passato come parametro
	 * @throws ElementoNonPresente sse non e' presente un cibo con il nome richiesto
	 * @throws DBException
	 */
	protected Element getCiboElement(String nome) throws ElementoNonPresente, DBException {
		Element cibo = null;
		try {
			cibo = (Element)XPath.selectSingleNode(getDB(), "/easycooking/cibi/cibo[@nome='" + nome + "']");
		} catch (JDOMException e) {
			throw new DBException(e);
		}
		if (cibo == null) throw new ElementoNonPresente("Nel database non e' presente un cibo con nome = '" + nome + "'");
		return cibo;
	}
}
